import {
  DualController,
  IAssemblyDocTreeNode,
  ITransformDetail,
} from '@amcax/base';
import { PickerAssembly, PickerMgr } from '@amcax/renderer';
import { PickableCommandForm } from '@app-cad/common/components/command-form/pickable';
import { pickInput3dLock } from '@app-cad/common/components/pick-input/3d';
import { PickedProduct } from '@app-cad/common/components/pick-input/pick-product';
import {
  identityMatrixElements,
  PRODUCT_EVENT,
} from '@app-cad/common/Constants';
import { csEvent } from '@app-cad/common/events/EventCenter';
import { eventCenterManager } from '@app-cad/common/events/EventCenterManager';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { DocumentContext } from '@app-cad/common/providers/documentProvider';
import { ctx2, userEvents } from '@app-cad/module';
import { useContext, useEffect, useRef, useState } from 'react';
import { Matrix4 } from 'three-legacy';
import { useDollyCamera } from '../hooks/useDollyCamera';
import {
  defaultTransformation,
  getExtraUiSchema,
  schema,
} from './form-definition';
import './PartAndTransformDialog.scss';
import { FormData, PartAndTransformCmdParams } from './types';

interface PartAndTransformDialogProps {
  title: string;
  onClose: VoidFunction;
  executeCmd: (param: PartAndTransformCmdParams) => Promise<void>;
  hideOriginal?: boolean;
}

export function PartAndTransformDialog({
  title,
  onClose,
  executeCmd,
  hideOriginal = false,
}: PartAndTransformDialogProps) {
  const { docTree } = useContext(DocumentContext);

  const [formData, setFormData] = useState<FormData>({
    select: null,
    ...defaultTransformation,
  });

  const selectRef = useRef(formData.select);
  selectRef.current = formData.select;

  const [matrix, setMatrix] = useState<Matrix4>();

  const transformTargetRef = useRef<PickedProduct>();
  const controllerRef = useRef<DualController>();
  const cmdID = 'cmd-transform';

  useDollyCamera();

  useEffect(() => {
    controllerRef.current = new DualController(ctx2);
    const abortController = new AbortController();
    const { signal } = abortController;

    window.addEventListener(
      'keydown',
      ({ key }: KeyboardEvent) => {
        if (key === 'Control') {
          controllerRef.current.detach();
          ctx2.viewer.requestRender();
        }
      },
      { signal },
    );
    window.addEventListener(
      'keyup',
      ({ key }: KeyboardEvent) => {
        if (key === 'Control' && transformTargetRef.current) {
          controllerRef.current.attach(transformTargetRef.current);
          ctx2.viewer.requestRender();
        }
      },
      { signal },
    );

    controllerRef.current.addEventListener(
      'transform',
      (evt) => {
        const customEvent = evt as CustomEvent<ITransformDetail>;

        const { matrix } = customEvent.detail;

        setFormData({
          select: selectRef.current,
          distance: controllerRef.current.distance,
          angle: controllerRef.current.rotationAngle,
        });

        setMatrix(matrix);

        ctx2.viewer.requestRender();
      },
      { signal },
    );

    userEvents.on(
      PRODUCT_EVENT.REMOVE_DOC,
      (removedDoc: IAssemblyDocTreeNode) => {
        if (removedDoc === transformTargetRef.current?.assembly) {
          onClose();
        }
      },
      signal,
    );

    return () => {
      abortController.abort();
      pickInput3dLock.unlock();

      if (transformTargetRef.current) {
        if (hideOriginal) {
          const picker = PickerMgr.getPicker<PickerAssembly>('assembly');
          picker.setFirstLevelChildVisibility(
            transformTargetRef.current.assembly.key,
            true,
          );
        }

        ctx2.cadScene.assisGroup.remove(transformTargetRef.current);
        transformTargetRef.current.destroy();
      }
      controllerRef.current.detach();
      controllerRef.current.destroy();

      eventCenterManager.switchTo('default');
    };
  }, []);

  const isFormDataValid = () =>
    formData?.select != null &&
    matrix &&
    identityMatrixElements.some(
      (value, index) => matrix.elements[index] !== value,
    );

  const checkAndFinish = async () => {
    if (!isFormDataValid()) {
      return;
    }

    await executeCmd({ formData, matrix: matrix, onClose, docTree });
  };

  useCommand(cmdID, {
    onClose,
    finishDraw: checkAndFinish,
  });

  const updateForm = (val: FormData) => {
    const { select, distance, angle } = val;

    if (select && select !== formData.select) {
      eventCenterManager.switchTo('empty');
      csEvent().setState(cmdID);

      pickInput3dLock.lock();
      setFormData({
        select,
        ...defaultTransformation,
      });

      transformTargetRef.current = select.assisObj.cloneSelf();
      ctx2.cadScene.assisGroup.add(transformTargetRef.current);

      controllerRef.current.attach(transformTargetRef.current);
      ctx2.viewer.requestRender();

      if (hideOriginal) {
        const picker = PickerMgr.getPicker<PickerAssembly>('assembly');
        picker.setFirstLevelChildVisibility(select?.idInfo?.o, false);
      }
    } else {
      if (!select) {
        return;
      }

      // Define the axes
      const axes = ['X', 'Y', 'Z'];

      // Calculate delta for each axis and apply translation and rotation
      axes.forEach((axis, index) => {
        const deltaDistance = distance[index] - formData.distance[index];
        const deltaAngle = angle[index] - formData.angle[index];

        transformTargetRef.current[`translate${axis}`](deltaDistance);
        transformTargetRef.current[`rotate${axis}`](
          (deltaAngle / 180) * Math.PI,
        );
      });

      controllerRef.current.onTransformEnd();
      ctx2.viewer.requestRender();

      setFormData(val);
    }
  };

  const extraUiSchema = getExtraUiSchema(formData);

  return (
    <PickableCommandForm
      disabled={false}
      isFormDataValid={isFormDataValid()}
      onClose={onClose}
      title={title}
      schema={schema}
      formData={formData}
      onChange={updateForm}
      extraUiSchema={extraUiSchema}
      submitText='完成'
      onSubmit={checkAndFinish}
    />
  );
}
